﻿using NetworkLib;
using NetworkLib.Utilities;
using PBO.Network.Domain;
using PBO.Network.Enumerations;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace PBO.Network.Server
{
    public delegate bool ClientConnectedDelegate(string identity, BattleMode modeInfo, string versionInfo, out string message);

    public class PokemonBattleServer : BattleServer, IBattleServerService
    {
        protected Dictionary<int, string> _playerList = new Dictionary<int, string>();
        protected List<int> _observerList = new List<int>();

        public PokemonBattleServer()
            : base((int)ServerPort.PokemonBattleServerPort)
        {
            base.OnClientDisconnected += PokemonBattleServer_OnClientDisconnected;
        }

        private void PokemonBattleServer_OnClientDisconnected(ClientSession client)
        {
            if (_playerList.ContainsKey(client.SessionID))
            {
                OnExit(client.SessionID, _playerList[client.SessionID]);
            }
            else if (_observerList.Contains(client.SessionID))
            {
                _observerList.Remove(client.SessionID);
            }
        }

        #region Events

        public event ClientConnectedDelegate OnBattleClientConnected;
        private bool VerifyClient(string identity, BattleMode modeInfo, string versionInfo, out string message)
        {
            if (OnBattleClientConnected != null)
            {
                return OnBattleClientConnected(identity, modeInfo, versionInfo, out message);
            }
            message = "";
            return true;
        }

        public event Action<PlayerMove> OnSetMove;
        public event Action<byte, string, byte[]> OnSetTeam;
        public event Action<string> OnPlayerExit;
        public event Action<int, string, TieMessage> OnTie;
        public event Func<BattleInfo> OnRequestBattleInfo;

        private BattleInfo HandleRequestBattleInfoEvent()
        {
            if (OnRequestBattleInfo != null) return OnRequestBattleInfo();
            return null;
        }

        #endregion

        #region send

        protected void SendAllPlayer(int method, object obj)
        {
            base.SendData(_playerList.Keys, method, obj);
        }

        public void SendRandomSeed(int seed)
        {
            SendAllPlayer(BattleMethods.CLIENT_RECEIVERANDOMSEED_HASH, seed);
        }

        public void SendRules(BattleRule[] rules)
        {
            var req = new BattleRuleSequence();
            req.Rules = rules;
            SendAllPlayer(BattleMethods.CLIENT_RECEIVERULES_HASH, req);
        }

        public void SendMove(PlayerMove move)
        {
            SendAllPlayer(BattleMethods.CLIENT_RECEIVEMOVE_HASH, move);
        }

        public void SendTeam(byte position, string identity, byte[] team)
        {
            var rsp = new BattleTeam() { position = position, identity = identity, team = team };
            SendAllPlayer(BattleMethods.CLIENT_RECEIVETEAM_HASH, rsp);
        }

        public void Exit(string identity)
        {
            SendAllPlayer(BattleMethods.CLIENT_EXIT_HASH, identity);
        }

        public void Tie(string identity, TieMessage message)
        {
            var req = new TieMessageRequest();
            req.identity = identity;
            req.message = message;
            SendAllPlayer(BattleMethods.CLIENT_EXIT_HASH, req);
        }

        public void TieExcept(int sessionID, string identity, TieMessage message)
        {
            var list = _playerList.Keys.Where(k => k != sessionID);
            var rsp = new TieMessageRequest() { identity = identity, message = message };
            base.SendData(list, BattleMethods.CLIENT_RECEIVETIEMESSAGE_HASH, rsp);
        }

        public void TieRequestFail(int sessionID)
        {
            var rsp = new TieMessageRequest() { identity = "", message = TieMessage.Fail };
            base.Send(sessionID, BattleMethods.CLIENT_RECEIVETIEMESSAGE_HASH, rsp);
        }

        public void SendBattleSnapshot(BattleSnapshot snapshot)
        {
            base.SendData(_observerList, BattleMethods.CLIENT_RECEIVEBATTLESNAPSHOT_HASH, snapshot);
        }

        public void SendBattleInfo(BattleInfo info)
        {
            base.SendData(_observerList, BattleMethods.CLIENT_RECEIVEBATTLEINFO_HASH, info);
        }

        #endregion

        #region IPokemonBattleServerService

        public void OnLogon(int sessionID, LogonResponse rsp)
        {
            string message;
            if (VerifyClient(rsp.identity, rsp.modeInfo, rsp.versionInfo, out message))
            {
                base.Send(sessionID, BattleMethods.CLIENT_LOGONSUCCESS_HASH, null);
                _playerList[sessionID] = rsp.identity;
            }
            else
            {
                base.Send(sessionID, BattleMethods.CLIENT_LOGONFAIL_HASH, message);
                Disconnect(sessionID);
            }
        }

        public void OnReceiveMove(int sessionID, PlayerMove move)
        {
            if (OnSetMove != null) OnSetMove(move);
        }

        public void OnReceiveTeam(int sessionID, BattleTeam rsp)
        {
            if (OnSetTeam != null) OnSetTeam(rsp.position, rsp.identity, rsp.team);
        }

        public void OnExit(int sessionID, string identity)
        {
            if (OnPlayerExit != null) OnPlayerExit(identity);
        }

        public void OnReceiveTieMessage(int sessionID, TieMessageRequest rsp)
        {
            if (OnTie != null) OnTie(sessionID, rsp.identity, rsp.message);
        }

        public void OnRegistObsever(int sessionID, int identity)
        {
            BattleInfo info = HandleRequestBattleInfoEvent();
            if (info != null)
            {
                base.Send(sessionID, BattleMethods.CLIENT_RECEIVEBATTLEINFO_HASH, info);
            }
            _observerList.Add(sessionID);
        }

        public void OnReceiveBattleInfo(int sessionID, BattleInfo info)
        { }
        public void OnReceiveBattleSnapshot(int sessionID, BattleSnapshot snapshot)
        { }
        public void OnTimeUp(int sessionID, string identity)
        { }

        #endregion

        protected override void OnReceiveData(int sessionID, int method, ByteArray data)
        {
            base.OnReceiveData(sessionID, method, data);
            switch (method)
            {
                case BattleMethods.SERVER_LOGON_HASH:
                    OnLogon(sessionID, DataByteArray.FromByteArray<LogonResponse>(data));
                    break;
                case BattleMethods.SERVER_RECEIVEMOVE_HASH:
                    OnReceiveMove(sessionID, DataByteArray.FromByteArray<PlayerMove>(data));
                    break;
                case BattleMethods.SERVER_RECEIVETEAM_HASH:
                    OnReceiveTeam(sessionID, DataByteArray.FromByteArray<BattleTeam>(data));
                    break;
                case BattleMethods.SERVER_EXIT_HASH:
                    OnExit(sessionID, data.ReadUTF());
                    break;
                case BattleMethods.SERVER_TIMEUP_HASH:
                    OnTimeUp(sessionID, data.ReadUTF());
                    break;
                case BattleMethods.SERVER_RECEIVETIEMESSAGE_HASH:
                    OnReceiveTieMessage(sessionID, DataByteArray.FromByteArray<TieMessageRequest>(data));
                    break;
                case BattleMethods.SERVER_REGISTOBSEVER_HASH:
                    OnRegistObsever(sessionID, data.ReadInt());
                    break;
                case BattleMethods.SERVER_RECEIVEBATTLEINFO_HASH:
                    OnReceiveBattleInfo(sessionID, DataByteArray.FromByteArray<BattleInfo>(data));
                    break;
                case BattleMethods.SERVER_RECEIVEBATTLESNAPSHOT_HASH:
                    OnReceiveBattleSnapshot(sessionID, DataByteArray.FromByteArray<BattleSnapshot>(data));
                    break;
            }
        }
    }
}
